import "./circled.scss";
import Cropper from "cropperjs";
import type { CSSProperties } from "vue";
import { useResizeObserver } from "@vueuse/core";
import { useTippy, directive as tippy } from "vue-tippy";
import { delay, debounce, isArray, downloadByBase64 } from "@pureadmin/utils";
import { EmitState } from "..";
import Menu from "./menu.vue";
import {
	ref,
	unref,
	computed,
	PropType,
	onMounted,
	onUnmounted,
	defineComponent,
	h,
	createVNode,
} from "vue";

type Options = Cropper.Options;

const defaultOptions: Options = {
	aspectRatio: 1,
	zoomable: true,
	zoomOnTouch: true,
	zoomOnWheel: true,
	cropBoxMovable: true,
	cropBoxResizable: true,
	toggleDragModeOnDblclick: true,
	autoCrop: true,
	background: true,
	highlight: true,
	center: true,
	responsive: true,
	restore: true,
	checkCrossOrigin: true,
	checkOrientation: true,
	scalable: true,
	modal: true,
	guides: true,
	movable: true,
	rotatable: true,
};

const props = {
	src: { type: String, required: true },
	alt: { type: String },
	height: { type: [String, Number], default: "360px" },
	/** 圆形方形 */
	circled: { type: Boolean, default: true },
	/** 实时获取文件信息 */
	realTimePreview: { type: Boolean, default: true },
	/** 配置 */
	options: { type: Object as PropType<Options>, default: () => ({}) },
	crossorigin: {
		type: String as PropType<"" | "anonymous" | "use-credentials" | undefined>,
		default: undefined,
	},
	imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
};

export default defineComponent({
	name: "ReCropper",
	props,
	setup(props, { attrs, emit }) {
		const tippyElRef = ref<HTMLImageElement>();
		const imgElRef = ref<HTMLImageElement>();
		const cropper = ref<Cropper>();
		const isReady = ref(false);
		const imgBase64 = ref();
		const inCircled = ref(props.circled);
		const inSrc = ref(props.src);
		let scaleX = 1;
		let scaleY = 1;

		const debounceRealTimeCroppered = debounce(realTimeCroppered, 80);

		const getImageStyle = computed((): CSSProperties => {
			return {
				height: props.height,
				maxWidth: "100%",
				...props.imageStyle,
			};
		});

		const getClass = computed(() => {
			return [
				attrs.class,
				{
					["re-circled"]: inCircled.value,
				},
			];
		});

		const getWrapperStyle = computed((): CSSProperties => {
			return { height: `${props.height}`.replace(/px/, "") + "px" };
		});

		onMounted(init);

		onUnmounted(() => {
			cropper.value?.destroy();
		});

		useResizeObserver(tippyElRef, () => {
			handCropper("reset");
		});

		/** 初始化 */
		async function init() {
			const imgEl = unref(imgElRef);
			if (!imgEl) return;
			cropper.value = new Cropper(imgEl, {
				...defaultOptions,
				ready: () => {
					isReady.value = true;
					realTimeCroppered();
					delay(400).then(() => emit("readied", cropper.value));
				},
				crop() {
					debounceRealTimeCroppered();
				},
				zoom() {
					debounceRealTimeCroppered();
				},
				cropmove() {
					debounceRealTimeCroppered();
				},
				...props.options,
			});
		}

		function realTimeCroppered() {
			props.realTimePreview && croppered();
		}

		/** 获取文件信息 */
		function croppered() {
			if (!cropper.value) return;
			const canvas = inCircled.value ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
			// https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob
			canvas.toBlob(blob => {
				if (!blob) return;
				const fileReader: FileReader = new FileReader();
				fileReader.readAsDataURL(blob);
				fileReader.onloadend = e => {
					if (!e.target?.result || !blob) return;
					imgBase64.value = e.target.result;
					emit("cropper", {
						base64: e.target.result,
						blob,
						info: { size: blob.size, ...cropper.value!.getData() },
					});
				};
				fileReader.onerror = () => {
					emit("error");
				};
			});
		}

		/** 获取画布信息 */
		function getRoundedCanvas() {
			const sourceCanvas = cropper.value!.getCroppedCanvas();
			const canvas = document.createElement("canvas");
			const context = canvas.getContext("2d")!;
			const width = sourceCanvas.width;
			const height = sourceCanvas.height;
			canvas.width = width;
			canvas.height = height;
			context.imageSmoothingEnabled = true;
			context.drawImage(sourceCanvas, 0, 0, width, height);
			context.globalCompositeOperation = "destination-in";
			context.beginPath();
			context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
			context.fill();
			return canvas;
		}

		/** 裁剪事件分发 */
		function handCropper(event: keyof Cropper, arg?: number | Array<number>) {
			if (event === "scaleX") {
				scaleX = arg = scaleX === -1 ? 1 : -1;
			}
			if (event === "scaleY") {
				scaleY = arg = scaleY === -1 ? 1 : -1;
			}
			// @ts-ignore
			arg && isArray(arg) ? cropper.value?.[event]?.(...arg) : cropper.value?.[event]?.(arg);
		}

		/** 上传之前 */
		function beforeUpload(file: File) {
			const reader = new FileReader();
			reader.readAsDataURL(file);
			inSrc.value = "";
			reader.onload = e => {
				inSrc.value = e.target?.result as string;
			};
			reader.onloadend = () => {
				init();
			};
			return false;
		}

		/** 下载 */
		function download(name: string = "zj") {
			downloadByBase64(imgBase64.value, name + ".png");
		}

		/** 切换选取形状 圆形或方形 */
		function switchShape() {
			inCircled.value = !inCircled.value;
			realTimeCroppered();
		}

		const menuContent = defineComponent({
			directives: { tippy },
			setup() {
				return () =>
					createVNode(Menu, {
						cropper: cropper,
						onChange: (event: keyof Cropper, args: any[]) => handCropper(event, args),
						onOtherOper: (flag: EmitState, file: File) => {
							flag === "upload" && beforeUpload(file);
							flag === "download" && download();
							flag === "switchShape" && switchShape();
						},
					});
			},
		});

		function onContextmenu(event: any) {
			event.preventDefault();
			const { show, setProps } = useTippy(tippyElRef, {
				content: menuContent,
				arrow: false,
				theme: "light",
				trigger: "manual",
				interactive: true,
				appendTo: "parent",
				// hideOnClick: false,
				animation: "perspective",
				placement: "auto",
			});

			setProps({
				getReferenceClientRect: () => ({
					width: 0,
					height: 0,
					top: event.clientY,
					bottom: event.clientY,
					left: event.clientX,
					right: event.clientX,
				}),
			});

			show();
		}

		return {
			inSrc,
			props,
			imgElRef,
			tippyElRef,
			getClass,
			getWrapperStyle,
			getImageStyle,
			isReady,
			croppered,
			onContextmenu,
		};
	},

	render() {
		const { inSrc, isReady, getClass, getImageStyle, onContextmenu, getWrapperStyle } = this;
		const { alt, crossorigin } = this.props;

		const rr = h(
			"div",
			{
				ref: "tippyElRef",
				class: getClass,
				style: getWrapperStyle,
				onContextmenu: (event: Event) => onContextmenu(event),
			},
			h("img", {
				onShow: isReady,
				ref: "imgElRef",
				style: getImageStyle,
				src: inSrc,
				alt: alt,
				crossorigin: crossorigin,
			})
		);

		return inSrc ? rr : null;
	},
});
